package task

import (
	"encoding/json"
	"net"
	"pids-cloud-server/app/common"
	"pids-cloud-server/config"
	"pids-cloud-server/constants"
	"pids-cloud-server/entity"
	"pids-cloud-server/logging"
	"pids-cloud-server/snmp"
	"pids-cloud-server/utils"
	"strconv"
	"sync"
	"time"

	"gorm.io/gorm"
)

var snmpTaskLock sync.Mutex

func SnmpTask() {
	logging.Info("---------定时采集监控数据任务开始---------")
	if !snmpTaskLock.TryLock() {
		logging.Warn("上一次SnmpTask还未结束，本次跳过")
		return
	}
	defer snmpTaskLock.Unlock()

	// 获取数据库连接
	db := utils.GetDB()
	if db == nil {
		logging.Error("数据库连接失败，跳过SNMP采集任务")
		return
	}

	start := time.Now()
	var systemInfos []entity.SystemInfo
	var snmpOids []entity.SnmpOid
	//查询所有的snmpinfo表数据
	var snmpInfos []entity.SnmpInfo
	db.Model(&entity.SnmpInfo{}).Where("enable", 1).Find(&snmpInfos)
	subQuery := db.Model(&entity.SnmpInfo{}).Select("ip").Where("enable = ?", 1)
	db.Where("host_name IN (?)", subQuery).Find(&systemInfos)
	db.Where("snmp_ip in(?)", subQuery).Find(&snmpOids)
	//logging.Debug("---------查询数据库耗时:", time.Since(start))
	ipsystemInfoMap := make(map[string]*entity.SystemInfo)
	for _, info := range systemInfos {
		ipsystemInfoMap[info.HostName] = &info
	}
	ipsnmpOidListMap := make(map[string][]entity.SnmpOid)
	for _, snmpoid := range snmpOids {
		ip := snmpoid.Snmpip
		// 将 snmpoid 添加到对应 IP 的列表中
		ipsnmpOidListMap[ip] = append(ipsnmpOidListMap[ip], snmpoid)
	}
	//logging.Debug("---------初始化好数据耗时：---------", time.Since(start))
	snmpTaskCfg := config.SnmpTaskCfg
	maxGoroutines := snmpTaskCfg.MaxGoroutines // 限制并发 goroutine 数量
	sem := make(chan struct{}, maxGoroutines)  // 使用带缓冲的 Channel 控制并发数量
	batchSize := snmpTaskCfg.BatchSize
	logging.Info("batchSize=", batchSize)
	var wg sync.WaitGroup // 创建WaitGroup
	for i := 0; i < len(snmpInfos); i += batchSize {
		end := i + batchSize
		if end > len(snmpInfos) {
			end = len(snmpInfos)
		}

		batch := snmpInfos[i:end]
		wg.Add(1) // 每启动一个协程，增加一个计数
		// 获取一个信号量
		sem <- struct{}{}
		go func(batch []entity.SnmpInfo, batchIndex int, db *gorm.DB) {
			var currentIP string // 新增变量
			defer wg.Done()      // 协程结束时减少计数
			// 释放一个信号量
			defer func() { <-sem }()
			// 捕获异常，防止协程崩溃
			defer func() {
				if r := recover(); r != nil {
					logging.Error("采集发生异常，当前设备IP:", currentIP, "异常信息:", r)
				}
			}()
			start1 := time.Now()
			for _, info := range batch {
				currentIP = info.IP // 每次循环更新
				start2 := time.Now()
				if info.Community == "" {
					handleNetworkDeviceCollection(db, &info, ipsystemInfoMap[info.IP])
				} else {
					handleSnmpCollection(db, &info, ipsystemInfoMap[info.IP], ipsnmpOidListMap[info.IP])
				}
				logging.Info("协程ID:", utils.GetGID(), "======单个设备", info.IP, "采集耗时："+time.Since(start2).String())
			}
			logging.Info("协程ID:", utils.GetGID(), "======第", batchIndex, "批次设备采集耗时："+time.Since(start1).String())
		}(batch, i/batchSize, db)
	}
	wg.Wait() // 等待所有协程完成
	logging.Info("定时采集监控数据任务结束---------耗时：", time.Since(start))
}

// 采集网络设备数据，仅ping
func handleNetworkDeviceCollection(db *gorm.DB, info *entity.SnmpInfo, systemInfo *entity.SystemInfo) {
	if systemInfo == nil {
		systemInfo = &entity.SystemInfo{}
	}
	systemInfo.Type = info.Devicetype
	start := time.Now()
	ping := utils.PING(info.IP)
	if time.Since(start) > time.Second {
		logging.Debug(info.IP, "网络ping耗时："+time.Since(start).String())
	}

	var networkstatus entity.NetWorkStatus
	if ping {
		networkstatus = entity.ONLINE
	} else {
		networkstatus = entity.OFFLINE
	}
	networkState := &entity.NetworkState{
		ID:         utils.GenerateID().Int64(),
		CreateTime: time.Now(),
		HostName:   info.IP,
		Status:     networkstatus,
		Type:       info.Devicetype,
	}
	networkstatejson, err := json.Marshal(networkState)
	if err != nil {
		logging.Error("网络状态序列化失败", err.Error())
	} else {
		utils.LPush(constants.NETWORK_STATE_LIST, networkstatejson)
	}
	systemInfo.NetworkState = networkstatus
	saveOrUpdateSystemInfo(db, systemInfo, snmp.DeviceInfo{}, info)
}

func handleSnmpCollection(db *gorm.DB, info *entity.SnmpInfo, systemInfo *entity.SystemInfo, snmpOids []entity.SnmpOid) {
	snmpOidMap := make(map[string]entity.SnmpOid)
	for _, oid := range snmpOids {
		snmpOidMap[oid.OidName] = oid
	}
	if systemInfo == nil {
		systemInfo = &entity.SystemInfo{}
	}
	systemInfo.Type = info.Devicetype
	start := time.Now()
	ping := utils.PING(info.IP)
	if time.Since(start) > time.Second {
		logging.Debug(info.IP, "网络ping耗时："+time.Since(start).String())
	}

	var networkstatus entity.NetWorkStatus
	if ping {
		networkstatus = entity.ONLINE
	} else {
		networkstatus = entity.OFFLINE
	}
	networkState := &entity.NetworkState{
		ID:         utils.GenerateID().Int64(),
		CreateTime: time.Now(),
		HostName:   info.IP,
		Status:     networkstatus,
		Type:       info.Devicetype,
	}
	networkstatejson, err := json.Marshal(networkState)
	if err != nil {
		logging.Error("网络状态序列化失败", err.Error())
	} else {
		utils.LPush(constants.NETWORK_STATE_LIST, networkstatejson)
	}
	if !ping {
		logging.Error("snmp采集任务", info.IP, "ping不通")
		if systemInfo.HostName == "" {
			saveOrUpdateSystemInfo(db, systemInfo, snmp.DeviceInfo{}, info)
		} else {
			//网络状态改变才更新时间，避免离线状态下不断更新createtime,这里只更新指定字段
			if systemInfo.NetworkState != networkstatus {
				systemInfo.CreatedAt = time.Now()
				systemInfo.AlarmState = entity.ABNORMAL
				logging.Infof("snmp采集任务，%s网络状态改变，更新时间", info.IP)
				// 获取数据库连接
				if db := utils.GetDB(); db != nil {
					db.Model(&systemInfo).Updates(map[string]interface{}{
						"network_state": networkstatus,
						//"alarm_state":   systemInfo.AlarmState, 告警状态统一在commitTask中用告警处理器处理
						"create_time": systemInfo.CreatedAt,
					})
				} else {
					logging.Error("数据库连接失败，跳过更新网络状态")
				}
			}
		}
		return
	}
	systemInfo.NetworkState = networkstatus

	// 初始化 SNMP 连接
	start1 := time.Now()
	acquisition := snmp.InitSnmpDataAcquisition(info)
	//Connect() 只是本地socket初始化，无法判断远端SNMP服务是否真的开启
	isAvailable := acquisition.CheckSnmpAvailable(snmpOidMap)
	if !isAvailable {
		logging.Error(info.IP, "SNMP 连接失败：")
		// SNMP连接失败时也要保存或更新systemInfo，至少更新网络状态
		saveOrUpdateSystemInfo(db, systemInfo, snmp.DeviceInfo{}, info)
		return
	}
	if time.Since(start1) > time.Second {
		logging.Debug(info.IP, "SNMP连接耗时："+time.Since(start1).String())
	}
	// 确保在所有采集完成后关闭连接
	defer func(Conn net.Conn) {
		err := Conn.Close()
		if err != nil {
			logging.Error("关闭SNMP连接失败：", err)
		}
	}(acquisition.Snmpclient.Conn)
	// 根据设备类型处理不同设备的采集逻辑
	switch info.Model {
	case entity.CENTOS, entity.UBUNTU: // 服务器
		handleServerSnmpCollection(db, acquisition, info, snmpOidMap, systemInfo)
	case entity.WINDOWS: //Windows系统
		handleWindowsSnmpCollection(db, acquisition, info, snmpOidMap, systemInfo)
	case entity.H3C_S7506E: // 交换机
		handleSwitchSnmpCollection(db, acquisition, info, snmpOidMap, systemInfo)
	case entity.MPDU: // PDU
		handlePduSnmpCollection(db, acquisition, info, snmpOidMap, systemInfo)
	}
	cpuState := &entity.CpuState{
		ID:         utils.GenerateID().Int64(),
		CreateTime: common.CustomTime{Time: time.Now()},
		HostName:   info.IP,
		Sys:        systemInfo.CpuPer,
		Type:       info.Devicetype,
	}
	cpustatejson, err := json.Marshal(cpuState)
	if err != nil {
		logging.Error("CPU状态序列化失败", err.Error())
	} else {
		utils.LPush(constants.CPU_STATE_LIST, cpustatejson)
	}
	memState := &entity.MemState{
		ID:         utils.GenerateID().Int64(),
		CreateTime: common.CustomTime{Time: time.Now()},
		HostName:   info.IP,
		UsePer:     systemInfo.MemPer,
		Type:       info.Devicetype,
	}
	memstatejson, err := json.Marshal(memState)
	if err != nil {
		logging.Error("内存状态序列化失败", err.Error())
	} else {
		utils.LPush(constants.MEM_STATE_LIST, memstatejson)
	}
}

func handleWindowsSnmpCollection(db *gorm.DB, acquisition *snmp.SnmpDataAcquisition, info *entity.SnmpInfo, snmpOidMap map[string]entity.SnmpOid, systemInfo *entity.SystemInfo) {
	start := time.Now()
	uptime, err := acquisition.AcquireUptime(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集运行时间失败", err.Error())
	} else {
		systemInfo.Uptime = uptime
		logging.Debug(info.IP, "设备运行时间：", uptime)
	}

	baseInfo, err := acquisition.AcquireBaseInfo(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集基础信息失败", err.Error())
	} else {
		logging.Debug(info.IP, "snmp采集基础信息", baseInfo)
		systemInfo.VersionDetail = baseInfo.DeviceName
		systemInfo.Version = baseInfo.DeviceName
	}

	serverAcquisition := &snmp.SnmpWindowsDataAcquisition{
		SnmpDataAcquisition: *acquisition,
	}
	cpu, err := serverAcquisition.AcquireCpu(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集cpu使用率失败", err.Error())
	} else {
		logging.Debug(info.IP, "cpu使用率：", cpu)
		systemInfo.CpuPer = cpu
	}

	memoryInfo, disk, err := serverAcquisition.AcquireMemoryAndStorageUsage(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集windows内存、磁盘信息失败", err.Error())
	} else {
		logging.Debug(info.IP, "内存信息：", memoryInfo)
		systemInfo.MemTotalSize = int64(memoryInfo.Total)
		systemInfo.MemPer = memoryInfo.MemoryUsage
	}

	if err != nil {
		logging.Error(info.IP, "snmp采集磁盘信息失败", err.Error())
	} else {
		logging.Debug(info.IP, "磁盘信息：", disk)
		systemInfo.DiskPer = disk.Usage
		sizestr := utils.GetSize(disk.TotalSize)
		systemInfo.DiskTotalSize, _ = strconv.ParseInt(sizestr[0:len(sizestr)-2], 10, 64)
		deskState := entity.DeskState{}
		deskState.HostName = info.IP
		deskState.CreateTime = common.CustomTime{Time: time.Now()}
		deskState.Avail = utils.GetSize(disk.AvailSize)
		deskState.Size = utils.GetSize(disk.TotalSize)
		deskState.Used = utils.GetSize(disk.UsedSize)
		deskState.UsePer = disk.Usage
		deskState.ID = utils.GenerateID().Int64()
		deskState.Type = info.Devicetype
		deskState.FileSystem = "/"
		deskstatejson, err := json.Marshal(deskState)
		if err != nil {
			logging.Error("磁盘状态序列化失败", err.Error())
		} else {
			utils.LPush(constants.DISK_STATE_LIST, deskstatejson)
		}
	}
	saveOrUpdateSystemInfo(db, systemInfo, baseInfo, info)
	if time.Since(start) > time.Second {
		logging.Info(info.IP + "采集服务器snmp耗时：" + time.Since(start).String())
	}
}
func handleServerSnmpCollection(db *gorm.DB, acquisition *snmp.SnmpDataAcquisition, info *entity.SnmpInfo, snmpOidMap map[string]entity.SnmpOid, systemInfo *entity.SystemInfo) {
	start := time.Now()
	uptime, err := acquisition.AcquireUptime(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集运行时间失败", err.Error())
	} else {
		systemInfo.Uptime = uptime
		logging.Debug(info.IP, "设备运行时间：", uptime)
	}

	baseInfo, err := acquisition.AcquireBaseInfo(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集基础信息失败", err.Error())
	} else {
		logging.Debug(info.IP, "snmp采集基础信息", baseInfo)
		systemInfo.VersionDetail = baseInfo.DeviceName
		systemInfo.Version = baseInfo.DeviceName
	}

	serverAcquisition := &snmp.SnmpServerDataAcquisition{
		SnmpDataAcquisition: *acquisition,
	}

	memoryInfo, err := serverAcquisition.AcquireMemoryUsage(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集内存信息失败", err.Error())
	} else {
		logging.Debug(info.IP, "内存信息：", memoryInfo)
		systemInfo.MemTotalSize = int64(memoryInfo.Total)
		systemInfo.MemPer = memoryInfo.MemoryUsage
	}

	cpu, err := serverAcquisition.AcquireCpu(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集cpu使用率失败", err.Error())
	} else {
		logging.Debug(info.IP, "cpu使用率：", cpu)
		systemInfo.CpuPer = cpu
	}

	disk, err := serverAcquisition.AcquireDisk(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集磁盘信息失败", err.Error())
	} else {
		logging.Debug(info.IP, "磁盘信息：", disk)
		systemInfo.DiskPer = disk.Usage
		sizestr := utils.GetSize(disk.TotalSize)
		systemInfo.DiskTotalSize, _ = strconv.ParseInt(sizestr[0:len(sizestr)-2], 10, 64)
		deskState := entity.DeskState{}
		deskState.HostName = info.IP
		deskState.CreateTime = common.CustomTime{Time: time.Now()}
		deskState.Avail = utils.GetSize(disk.AvailSize)
		deskState.Size = utils.GetSize(disk.TotalSize)
		deskState.Used = utils.GetSize(disk.UsedSize)
		deskState.UsePer = disk.Usage
		deskState.ID = utils.GenerateID().Int64()
		deskState.Type = info.Devicetype
		deskState.FileSystem = "/"
		deskstatejson, err := json.Marshal(deskState)
		if err != nil {
			logging.Error("磁盘状态序列化失败", err.Error())
		} else {
			utils.LPush(constants.DISK_STATE_LIST, deskstatejson)
		}
	}
	saveOrUpdateSystemInfo(db, systemInfo, baseInfo, info)
	if time.Since(start) > time.Second {
		logging.Info(info.IP + "采集服务器snmp耗时：" + time.Since(start).String())
	}
}

func handleSwitchSnmpCollection(db *gorm.DB, acquisition *snmp.SnmpDataAcquisition, info *entity.SnmpInfo, snmpOidMap map[string]entity.SnmpOid, systemInfo *entity.SystemInfo) {
	start := time.Now()
	switchAcquisition := &snmp.SnmpSwitchDataAcquisition{
		SnmpDataAcquisition: *acquisition,
	}
	baseInfo, err := acquisition.AcquireBaseInfo(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集基础信息失败", err.Error())
	} else {
		logging.Info(info.IP, "snmp采集基础信息", baseInfo)
		systemInfo.VersionDetail = baseInfo.DeviceName
		systemInfo.Version = baseInfo.DeviceName
	}
	uptime, err := acquisition.AcquireUptime(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集运行时间失败", err.Error())
	} else {
		systemInfo.Uptime = uptime
		logging.Debug(info.IP, "设备运行时间：", uptime)
	}
	cpu, err := switchAcquisition.AcquireCpu(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集cpu使用率失败", err.Error())
	} else {
		logging.Debug(info.IP, "cpu使用率：", cpu)
		systemInfo.CpuPer = cpu
	}
	memoryInfo, err := switchAcquisition.AcquireMemoryUsage(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集内存信息失败", err.Error())
	} else {
		logging.Debug(info.IP, "内存信息：", memoryInfo)
		systemInfo.MemTotalSize = int64(memoryInfo.Total)
		systemInfo.MemPer = memoryInfo.MemoryUsage
	}
	// 处理交换机相关数据采集
	portInfos, err := switchAcquisition.AcquirePortInfo(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集端口信息失败", err.Error())
	}

	temperature, err := switchAcquisition.AcquireTemperature(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集温度信息失败", err.Error())
	}
	serialNumber, err := switchAcquisition.AcquireSerialNumber(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集序列号失败", err.Error())
	}
	data := map[string]interface{}{
		"portInfos":    portInfos,
		"temperature":  temperature,
		"serialNumber": serialNumber,
	}
	extraJSON, _ := json.Marshal(data)
	systemInfo.Json = string(extraJSON)
	if time.Since(start) > time.Second {
		logging.Debug(info.IP + "采集交换机snmp耗时：" + time.Since(start).String())
	}
	saveOrUpdateSystemInfo(db, systemInfo, baseInfo, info)
}

func handlePduSnmpCollection(db *gorm.DB, acquisition *snmp.SnmpDataAcquisition, info *entity.SnmpInfo, snmpOidMap map[string]entity.SnmpOid, systemInfo *entity.SystemInfo) {
	start := time.Now()
	baseInfo, err := acquisition.AcquireBaseInfo(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集基础信息失败", err.Error())
	}
	systemInfo.VersionDetail = baseInfo.DeviceName
	systemInfo.Version = baseInfo.DeviceName
	uptime, err := acquisition.AcquireUptime(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集运行时间失败", err.Error())
	} else {
		systemInfo.Uptime = uptime
		logging.Info(info.IP, "运行时间：", uptime)
	}
	pduAcquisition := &snmp.SnmpPduDataAcquisition{
		SnmpDataAcquisition: *acquisition,
	}
	pduStateInfos, err := pduAcquisition.AcquirePduVoltage(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集电压失败", err.Error())
	} else {
		logging.Info(info.IP, "电压：", pduStateInfos)
	}

	pduSocketInfos, err := pduAcquisition.AcquirePduSocket(snmpOidMap)
	if err != nil {
		logging.Error(info.IP, "snmp采集电流失败", err.Error())
	} else {
		logging.Info(info.IP, "电流：", pduSocketInfos)
	}
	data := map[string]interface{}{
		"pdustate":   pduStateInfos,
		"pdusockets": pduSocketInfos,
	}
	extraJSON, _ := json.Marshal(data)
	systemInfo.Json = string(extraJSON)
	if time.Since(start) > time.Second {
		logging.Debug(info.IP + "采集电源控制器snmp耗时：" + time.Since(start).String())
	}
	// 处理PDU相关数据采集
	saveOrUpdateSystemInfo(db, systemInfo, baseInfo, info)
}

func saveOrUpdateSystemInfo(db *gorm.DB, systemInfo *entity.SystemInfo, baseInfo snmp.DeviceInfo, info *entity.SnmpInfo) {

	// 保存或更新 SystemInfo 到数据库
	if systemInfo.HostName == "" {
		systemInfo.ID = utils.GenerateID().Int64()
		systemInfo.HostName = info.IP
		systemInfo.CreatedAt = time.Now()
		systemInfo.Version = baseInfo.DeviceName
		systemInfo.VersionDetail = baseInfo.DeviceName
		systemInfo.AlarmState = entity.NORMAL
		// 对于纯网络设备（未采集CPU、内存、磁盘），将未采集的指标设为NULL而非0
		if info.Community == "" {
			// 使用指针类型，允许设置NULL值
			var updateMap = map[string]interface{}{
				"id":              systemInfo.ID,
				"host_name":       systemInfo.HostName,
				"create_time":     systemInfo.CreatedAt,
				"version":         nil,
				"version_detail":  nil,
				"alarm_state":     systemInfo.AlarmState,
				"network_state":   systemInfo.NetworkState,
				"type":            info.Devicetype,
				"cpu_per":         nil, // 设置为NULL
				"mem_per":         nil, // 设置为NULL
				"disk_per":        nil, // 设置为NULL
				"mem_total_size":  nil, // 设置为NULL
				"disk_total_size": nil, // 设置为NULL
			}
			db.Model(&entity.SystemInfo{}).Create(updateMap)
			return
		}
		db.Create(&systemInfo)
	} else {
		// 对于纯网络设备（未采集CPU、内存、磁盘），更新时将未采集的指标设为NULL而非0
		if info.Community == "" {
			db.Model(&systemInfo).Updates(map[string]interface{}{
				"version":         baseInfo.DeviceName,
				"version_detail":  baseInfo.DeviceName,
				"network_state":   systemInfo.NetworkState,
				"create_time":     time.Now(),
				"json":            systemInfo.Json,
				"cpu_per":         nil, // 设置为NULL
				"mem_per":         nil, // 设置为NULL
				"disk_per":        nil, // 设置为NULL
				"mem_total_size":  nil, // 设置为NULL
				"disk_total_size": nil, // 设置为NULL
				"uptime":          nil, // 设置为NULL
			})
			return
		}
		db.Model(&systemInfo).Updates(map[string]interface{}{
			"version":         baseInfo.DeviceName,
			"version_detail":  baseInfo.DeviceName,
			"disk_total_size": systemInfo.DiskTotalSize,
			"disk_per":        systemInfo.DiskPer,
			"uptime":          systemInfo.Uptime,
			"mem_total_size":  systemInfo.MemTotalSize,
			"network_state":   systemInfo.NetworkState,
			"mem_per":         systemInfo.MemPer,
			"cpu_per":         systemInfo.CpuPer,
			"create_time":     time.Now(),
			"json":            systemInfo.Json,
		})
	}
}
